package network;

import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;

public class HttpProxyServer {
    private static final Logger log = LogFactory.getLogger(HttpProxyServer.class);

    public static void main(String[] args) throws Exception {
        HttpServer httpServer = HttpServer.create(new InetSocketAddress(80), 0);
        HttpURLConnection.setFollowRedirects(false);
        HttpContext httpContext = httpServer.createContext("/", exchange -> {
            try {
                jdk8Http(exchange);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        httpContext.getFilters().add(new CorsFilter());
        // Thread.setDefaultUncaughtExceptionHandler((t, e) -> e.printStackTrace());
        httpServer.setExecutor(Executors.newWorkStealingPool(Runtime.getRuntime().availableProcessors()));
        httpServer.start();
    }

    static void jdk8Http(HttpExchange exchange) throws IOException {
        URL url = new URL("http://www.baidu.com" + exchange.getRequestURI().getPath());
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        connection.setRequestMethod(exchange.getRequestMethod());
        Set<Map.Entry<String, List<String>>> headers = exchange.getRequestHeaders().entrySet();
        for (Map.Entry<String, List<String>> header : headers) {
            connection.setRequestProperty(header.getKey(), header.getValue().get(0));
        }
        if ("POST".equals(exchange.getRequestMethod())) {
            connection.setDoOutput(true);
            i2o(exchange.getRequestBody(), connection.getOutputStream());
        }

        while (connection.getResponseCode() == HTTP_MOVED_TEMP || connection.getResponseCode() == HTTP_MOVED_PERM) {
            connection.disconnect();
            connection = (HttpURLConnection) new URL(connection.getHeaderField("Location")).openConnection();
        }
        headers = connection.getHeaderFields().entrySet();
        for (Map.Entry<String, List<String>> header : headers) {
            if (header.getKey() == null) continue;
            exchange.getResponseHeaders().put(header.getKey(), header.getValue());
        }
        exchange.sendResponseHeaders(connection.getResponseCode(), connection.getContentLengthLong());
        i2o(connection.getInputStream(), exchange.getResponseBody());
        // exchange.sendResponseHeaders(HTTP_OK, "ok".getBytes(StandardCharsets.UTF_8).length);
        // exchange.getResponseBody().write("ok".getBytes(StandardCharsets.UTF_8));

        exchange.close();
        connection.disconnect();
    }

    static void i2o(InputStream is, OutputStream os) throws IOException {
        int len;
        byte[] buffer = new byte[4096];
        // ByteArrayOutputStream baos = new ByteArrayOutputStream();
        while ((len = is.read(buffer)) != -1) {
            os.write(buffer, 0, len);
            // baos.write(buffer, 0, len);
        }
        is.close();
        os.close();
        // baos.close();
        // System.out.println(baos.toString(StandardCharsets.UTF_8));
    }

    // private static final HttpClient httpClient = HttpClient.newBuilder().build();

    // static void jdk11Http(HttpExchange exchange) throws IOException {
    //     HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(new URI("https://www.sina.com.cn"));
    //
    //     Set<Map.Entry<String, List<String>>> headers = exchange.getRequestHeaders().entrySet();
    //     for (Map.Entry<String, List<String>> header : headers) {
    //         if ("Connection".equals(header.getKey()) || "Host".equals(header.getKey())) continue;
    //         requestBuilder.header(header.getKey(), header.getValue().get(0));
    //     }
    //     if ("POST".equals(exchange.getRequestMethod())) {
    //         requestBuilder.POST(HttpRequest.BodyPublishers.ofInputStream(exchange::getRequestBody));
    //     }
    //
    //     HttpResponse<byte[]> response = httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
    //     while (response.statusCode() == HTTP_MOVED_TEMP || response.statusCode() == HTTP_MOVED_PERM) {
    //         HttpRequest request = HttpRequest.newBuilder(new URI(response.headers().allValues("Location").get(0))).build();
    //         response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
    //     }
    //     headers = response.headers().map().entrySet();
    //     for (Map.Entry<String, List<String>> header : headers) {
    //         if (header.getKey() == null) continue;
    //         exchange.getResponseHeaders().put(header.getKey(), header.getValue());
    //     }
    //     exchange.sendResponseHeaders(response.statusCode(), response.body().length);
    //     exchange.getResponseBody().write(response.body());
    //
    //     exchange.close();
    // }

    static class CorsFilter extends Filter {
        @Override
        public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
            if (exchange.getRequestHeaders().get("Origin") != null && exchange.getRequestHeaders().get("Host") != null &&
                !exchange.getRequestHeaders().get("Origin").get(0).equals(exchange.getRequestHeaders().get("Host").get(0))) {
                log.info(this::description);
                exchange.getResponseHeaders().put("Access-Control-Allow-Origin", exchange.getRequestHeaders().get("Origin"));
                if (exchange.getRequestHeaders().get("Access-control-request-headers") != null) {
                    // exchange.getResponseHeaders().put("Access-Control-Expose-Headers", exchange.getRequestHeaders().get("Access-control-request-headers"));
                    exchange.getResponseHeaders().put("Access-Control-Allow-Headers", exchange.getRequestHeaders().get("Access-control-request-headers"));
                }
                // if (exchange.getRequestHeaders().get("Access-control-request-method") != null) {
                //     exchange.getResponseHeaders().put("Access-Control-Allow-Methods", exchange.getRequestHeaders().get("Access-control-request-method"));
                // }
                // exchange.getResponseHeaders().put("Access-Control-Max-Age", Collections.singletonList("1728000"));
                exchange.getResponseHeaders().put("Access-Control-Allow-Credentials", Collections.singletonList("true"));
                exchange.getResponseHeaders().put("Set-cookie", Collections.singletonList("name=jhxxb; samesite=none; secure"));
            }
            if ("OPTIONS".equals(exchange.getRequestMethod())) {
                exchange.sendResponseHeaders(200, 0);
            } else {
                chain.doFilter(exchange);
            }
        }

        @Override
        public String description() {
            return "https://enable-cors.org";
        }
    }

    static class LogFactory {
        private static final Formatter formatter = new Formatter() {
            @Override
            public String format(LogRecord record) {
                String throwable = "";
                if (record.getThrown() != null) {
                    StringWriter sw = new StringWriter();
                    PrintWriter pw = new PrintWriter(sw);
                    pw.println();
                    record.getThrown().printStackTrace(pw);
                    pw.close();
                    throwable = "\n" + sw;
                }
                StackTraceElement stackTrace = Thread.currentThread().getStackTrace()[8];
                return String.format("%s [%s] (%s:%d) %s%s\n",
                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS")),
                    Thread.currentThread().getName(),
                    stackTrace.getFileName(),
                    stackTrace.getLineNumber(),
                    formatMessage(record),
                    throwable);
            }
        };

        public static <T> Logger getLogger(Class<T> logClass) {
            Logger logger = Logger.getLogger(logClass.getName());
            logger.setUseParentHandlers(false); // 禁用原输出，否则会输出两次
            ConsoleHandler consoleHandler = new ConsoleHandler();
            consoleHandler.setFormatter(formatter);
            logger.addHandler(consoleHandler);
            // try {
            //     FileHandler fileHandler = new FileHandler("", true); // true 表示日志内容在文件中追加
            //     fileHandler.setLevel(Level.ALL); // 级别为 ALL，记录所有消息
            //     fileHandler.setFormatter(formatter);
            //     logger.addHandler(fileHandler);
            // } catch (Exception e) {
            //     e.printStackTrace();
            // }
            return logger;
        }
    }
}